#include <iondrive/std.h>
#include <iondrive/glsl.h>

using namespace iondrive::glsl;

template<class T,int N,class U=T>
struct Test
{


	
	/* we can do anything std::complex<T> like here, but NOT MATH
	 * FUNCTIONS :( or anything requireing that */
	
	int test_all_complex_like()
	{
		std::cout
				<< "test_all_complex_like: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;

		int retval = 0;
		
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

		// since we now derive from std::array, we can do all
		// stl things.
		{

			vec<int,19> x;
			std::vector<int> v,u;
			
			if (1+1==3)	// we dont actually want to do this...
			{
				std::copy(x.begin(),x.end(),u.begin());
			}
			
		}
		
		// test things that can be done even with
		// complex numbers.


		// one-dimensional
		{

			T x,y,z;		// initialize to 0

			// one-dimensional: as you can see, we miss
			// almost all sin() like functionality in
			// complex, but we have a remaining  few here below
			
			y = radians(x);
			y = degrees(x);

			z = dot(x,y);
			z = normalize(x);
			z = reflect(x,y);

		}
		
		// vec<T,N> construction, member access.
		{
			vec<T,N> default_constructor;
			vec<T,N> members_identical(T(1));
			vec<T,N-1> a;
			vec<T,N> up_cast(a,T(2));
			vec<T,N+1> b;
			vec<T,N> down_cast(b);

			// member access
			a[0] = T(1);
			T tmp = a[0];

			// REQUIRED: C++ *cannot* do partial template
			// specialization in the constructor, so here are the
			// init_xxx() functions to fix this.

		}
		

		{
			// REQUIRED: vector operators

			vec<T,N> x,y,z;
			T a,b,c;


			vec<T,2> tmp10 = init_vec(a,b);
			vec<T,3> tmp11 = init_vec(a,b,c);
			vec<T,4> tmp12 = init_vec(a,b,c,a);

			x = y + z;
			x = y - z;
			x = y * z;

			// NOTICE: you must use 'typename' here in the
			// declaration of this variable or else get a
			// compiler error. 
			typename vec<T,N>::iterator tmp;
			for (tmp=z.begin();tmp!=z.end();tmp++)
				*tmp = T(1);

			// this is obviously the better way to do it
			std::fill(z.begin(),z.end(),T(1));
			
			z = y / z;

			y = a * x; y = x * a;
			y = a + x; y = x + a;
			y = a - x; y = x - a;
			y = x / T(1);

			y += x; y -= x; y *= x; y /= x;
			y += a; y -= a; y *= a; y /= a;

			y = -x;


		}


		{
			vec<T,N> x,y,z;
			T a,b,c;

			// REQUIRED: vector functions

			y = radians(x);
			y = degrees(x);

			c = dot(x,y);


			// saru and dot()
			a = z.dot(y);
			a = z.squared_length();
		}


		// NON-COPY-CREATING member interface
		{
			vec<T,N> x,y,z;
			T a,b,c;
			T array[N];

			x.init_vec(a);
			x.init_vec(y);
			x.init_vec(array);

			z.add(y); z.sub(y); z.mul(y); z.div(y);
			z.add(x,y); z.sub(x,y); z.mul(x,y); z.div(x,y);
			z.add(a); z.sub(a); z.mul(a); z.div(a);
			z.add(x,a); z.sub(x,a); z.mul(x,a); z.div(x,a);

			z.neg(); z.neg(y);

			z.radians();
			z.radians(y);
			z.degrees();
			z.degrees(y);

			a = z.dot(y);
			a = z.squared_length();
		}

		
		// matrices
		{
			mat<T,N> A,B,C;
			vec<T,N> x,y;
			T a;

			A = B + C;
			A = B - C;
			A = a + B; A = B + a;
			A = a - B; A = B - a;
			A = a * B; A = B * a;

			// these are the ones to optimize
			A = B * C;
			y = A * x;

			A = matrixcompmult(B,C);
			A = transpose(B);
			A = diag(y);
		}

	return retval;
	}







	/*
	 * Here we only need to test the additional math functions, and
	 * the saru memberwise interface.
	 */


	int test_all()
	{


		std::cout
				<< "test_all: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;




		int retval = 0;
		
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

		retval = test_all_complex_like(); // we can do everything a complex can do
		
		// test everything that can be done N-dimensional
		{
			T x,y,z;		// initialize to 0

			// cmath type functions must also work
			y = iondrive::math::abs(x);
			y = iondrive::math::acos(x);
			y = iondrive::math::asin(x);
			y = iondrive::math::atan(x);
			y = iondrive::math::atan2(x,y);
			y = iondrive::math::ceil(x);
			y = iondrive::math::cos(x);
			y = iondrive::math::exp(x);
			y = iondrive::math::fabs(x);
			y = iondrive::math::floor(x);
			y = iondrive::math::fmod(x,y);
			y = iondrive::math::log(x);
			y = iondrive::math::pow(x,y);
			y = iondrive::math::sin(x);
			y = iondrive::math::sqrt(x);
			y = iondrive::math::tan(x);

			// one-dimensional glsl functions

			y = exp2(x);
			y = log2(x);
			y = inversesqrt(x);
			y = sign(x);
			y = fract(x);
			z = mod(x,y);
			z = iondrive::glsl::min(x,y);
			z = iondrive::glsl::max(x,y);
			z = clamp(x,y,z);
			z = mix(x,y,z);
			z = step(x,y);
			z = smoothstep(x,y,z);
			z = length(x);
			z = distance(x,y);
			z = faceforward(x,y,z);
		}

		{
			vec<T,N> x,y,z;
			T a,b,c;
			
			// REQUIRED: vector functions

			y = sin(x);
			y = cos(x);
			y = tan(x);
			y = asin(x);
			y = acos(x);
			y = atan(x);
			y = atan(x,z);
			y = pow(x,z);
			y = exp2(x);
			y = log2(x);
			y = exp(x);
			y = log(x);
			y = sqrt(x);
			y = inversesqrt(x);
			y = abs(x);
			y = sign(x);
			y = floor(x);
			y = ceil(x);
			y = fract(x);
			y = mod(x,a); y = mod(x,z);
			y = min(x,a); y = min(x,z);
			y = max(x,a); y = max(x,z);
			y = clamp(x,a,b); y = clamp(x,z,y);
			y = mix(x,z,a); y = mix(x,y,z);
			y = step(z,x);
			y = smoothstep(x,y,z);
			y = normalize(x);
			y = faceforward(x,y,z);
			y = reflect(x,z);
			a = length(x);
			b = distance(x,z);


		}
		
		// NON-COPY-CREATING member interface
		{
			vec<T,N> x,y,z;
			T a,b,c;
			T array[N];

			z.sin(); z.sin(y);
			z.cos(); z.tan(y);
			z.asin(); z.asin(y);
			z.acos(); z.acos(y);
			z.atan(); z.atan(y); z.atan2(y); z.atan(x,y);
			z.pow(y); z.pow(x,y);
			z.exp2(); z.exp2(y);
			z.log2(); z.log2(y);
			z.exp(); z.exp(y);
			z.log(); z.log(y);
			z.sqrt(); z.sqrt(y);
			z.inversesqrt(); z.inversesqrt(y);
			z.abs(); z.abs(y);
			z.sign(); z.sign(y);
			z.floor(); z.floor(y);
			z.ceil(); z.ceil(y);
			z.fract(); z.fract(y);
			
			z.mod(a); z.mod(x,y); // TODO - should we not have z.mod(y) also?
			z.min(a); z.min(x,y);
			z.max(a); z.max(x,y);
			
			z.clamp(); z.clamp(a,b); z.clamp(x,a,b); z.clamp(x,y); z.clamp(x,y,z);
			z.mix(x,y,a); z.mix(x,y,z);
			z.step(x,y);
			z.smoothstep(x,y,z);

			z.normalize();
			z.normalize(y);
			z.faceforward(x,y,z);
			z.reflect(x,y);

			a = z.length();
			a = z.distance(x,y);
		}


		// matrices
		{
			mat<T,N> A,B,C;
			vec<T,N> x,y;
			T a;


			//
			// currently everything can be implemented
			// complex<T>
			// 
		}

		

		return retval;
	}


	



	

	int test_all_3d()
	{
		std::cout
				<< "test_all_3d: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;

		int retval = 0;
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

		retval = test_all();

		// test cross() and other 3d specializations

		{
			vec<T,N> x,y,z;

			z = cross(x,y);

			// member api
			z.cross(x,y);
			z.cross(y);
			z = x.unit_cross(y);
		}
		
		return retval;
	}









	int test_all_3d_complex_like()
	{
		std::cout
				<< "test_all_3d_complex_like: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;

		int retval = 0;
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

		retval = test_all_complex_like();

		// test cross() and other 3d specializations
		//
		// YES, cross() actually works in complex
		// YES, code is duplicated from test_all() :)

		v1 = cross(v2,v3);
		
		// member api
		v1.cross(v2,v3);
		v1.cross(v2);
		
		return retval;
	}











	int test_all_boolean_like_unordered()	// N-dimensinal boolean vectors
	{
		std::cout
				<< "test_all_boolean_like: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;

		int retval = 0;
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

			// boolean vectors
		vec<T,N> bX, bY;

		vec<U,N> x,y,z;


		bX = equal(y,z);
		bX = notEqual(y,z);

		T mybool;

		mybool = any(bX);
		mybool = all(bX);




#ifndef __GNUC__			// GNU c braindamage?
		bY = not(bX);
#endif
		bY = !bX;



		return retval;
	}

	int test_all_boolean_like()	// N-dimensinal boolean vectors
	{
		std::cout
				<< "test_all_boolean_like: N=" << N << ", sizeof(T)=" << sizeof(T) << ", sizeof(vec<T,N>)="
				<< sizeof(vec<T,N>) << ", sizeof(mat<T,N>)=" << sizeof(mat<T,N>) << "." << std::endl;

		int retval = 0;
		vec<T,N> v1, v2, v3;
		mat<T,N> m1, m2, m3;

		test_all_boolean_like_unordered();

		// boolean vectors
		vec<T,N> bX, bY;
		vec<U,N> x,y,z;



		// Below this line things only work when T is an
		// ordered body, so that is has >,>=,<,<= operators.
		//
		// So, complex<T> user types wont work :-)

		bX = lessThan(y,z);
		bX = lessThanEqual(y,z);
		bX = greaterThan(y,z);
		bX = greaterThanEqual(y,z);

		// often used example
		if ( all(lessThan(x,y)) || all(greaterThanEqual(y,z)) ) {
				/* All elements of x are smaller than all
				 * corresponding elements in y. This however
				 * creates a temporary boolean vector, what we
				 * want to avoid. */
		}




		return retval;
	}


}; // end Test<T,N> class







// BEGIN: user-defined class for unit test
class MyT
{
	public:
		MyT() {}
		MyT(const MyT&) {}
		MyT(const float) {}

		inline MyT operator=(const MyT& rhs) { return MyT(); }
};

MyT operator+=(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator-=(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator*=(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator/=(const MyT& lhs,const MyT& rhs) { return MyT(); } 

MyT operator+(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator-(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator*(const MyT& lhs,const MyT& rhs) { return MyT(); } 
MyT operator/(const MyT& lhs,const MyT& rhs) { return MyT(); }

MyT operator-(const MyT& t) { return MyT(); }
// --- equality
bool operator==(const MyT& lhs,const MyT& rhs) { return true; } 
bool operator!=(const MyT& lhs,const MyT& rhs) { return false; } 
// END: user-defined class for unit test




int main(void)
{
    int retval = 0;

    // unit test for glsl
    {
	    
	    // test everything except 3d specialization
	    retval = Test<float,250>().test_all();
	    retval = Test<long double,20>().test_all(); // long double

	    // also test cross() etc
	    retval = Test<float,3>().test_all_3d();

	    // test for boolean type interface *only*. a boolean is not
	    // a number.
	    retval = Test<bool,4,double >().test_all_boolean_like();


	    // Why is std::complex not working with boolean vectors??
	    //
	    // Because: complex<T> is NOT an ordered body, it has no
	    // concept of <,<=,>,>= operators...
	    //
	    retval = Test<bool,4,std::complex<double> >().test_all_boolean_like_unordered();
	    retval = Test<bool,6,MyT>().test_all_boolean_like_unordered();


	    // this one tests things that make sense in the complex
	    // number set, such as matrix operations and memberwise
	    // complex sin() etc
	    retval = Test< std::complex<float>,100>().test_all_complex_like();
	    retval = Test< std::complex<double>,3>().test_all_3d_complex_like();
	    retval = Test< std::complex<long double>,4>().test_all_complex_like();



	    // Just trying the other builtin types for fun.
	    retval = Test<char,10>().test_all_complex_like();
	    retval = Test<unsigned char,10>().test_all_complex_like();
	    retval = Test<short,10>().test_all_complex_like();
	    retval = Test<unsigned short,10>().test_all_complex_like();
	    retval = Test<int,10>().test_all_complex_like();
	    retval = Test<unsigned int,10>().test_all_complex_like();
	    retval = Test<long,10>().test_all_complex_like();
	    retval = Test<unsigned long,10>().test_all_complex_like();
	    retval = Test<long long,10>().test_all_complex_like();
	    retval = Test<unsigned long long,10>().test_all_complex_like();

	    // User-defined type matrices and vectors of dimension 10
	    retval = Test<MyT,10>().test_all_complex_like();
	    
	    // ColorRGB and ColorRGBA related
	    vec3 x3,y3=scale_by_max(x3);
	    vec4 x4,y4=scale_by_max(x4);
    }
    return retval;
}



//
// ### functions below are for checking generated asm code
//

extern "C" {
void STEP(const int value){}
}




template<class T,int N> float tulim(void)
{
	vec<T,N> x(1.0f), y, z(2.0f);
	mat<T,N> A, B, C;

	STEP(1);

	A = transpose(diag(x));
	B = diag(x);
	C = A * B;
	y = C * z;

	STEP(2);

	std::cout << "[" << N << "] " << std::flush;

	return length(y);
}
template<class T>
float test_upperlimit()
{
	return
			tulim<T,1>() +
			tulim<T,2>() +
			tulim<T,3>() +
			tulim<T,4>() ;
}

template<class T> bool test_sin()
{
	extern T a_init, b_init;

	vec3 a(a_init), b(b_init);

	STEP(1);

	a = sin(b);

	STEP(2);

	return any(lessThan(a,b));
}

template<class T> bool test_add()
{
	extern T b_init, c_init;

	vec3 a, b(b_init), c(c_init);

	STEP(1);

	a = b + c;

	STEP(2);

	return any(lessThan(a,b));
}




int main_test_vecmat234();
int main_test_genfloat();

template<class T,int N> int main_test_gen(const T& unity)
{

	vec<T,N> x,y;
	mat<T,N> A;

	STEP(1);			// initializy x

	for (int i = 0; i < N; i++) x[i] = unity;

	STEP(2);			// initialize A (creates a temp copy)

	A = diag(x);

	STEP(3);			// perform the vector expr

	y = A*x;

	STEP(4);

	return 0;
}

int main_test_genfloat()
{
	STEP(1);			// call main_test_gen

	main_test_gen<float,500>(1.0f);

	STEP(2);			// bow out

	return 0;
}



int main_test_vecmat234()
{

	vec2 x2,y2; mat2 A2;
	vec3 x3,y3; mat3 A3;
	vec4 x4,y4; mat4 A4;

	STEP(1);

	x2 = init_vec(1.0f,1.0f);
	A2 = init_mat(1.0f,0.0f,
		      0.0f,1.0f);

	STEP(2);

	x3 = init_vec(1.0f,1.0f,1.0f);
	A3 = init_mat(1.0f,0.0f,0.0f,
		      0.0f,1.0f,0.0f,
		      0.0f,0.0f,1.0f);

	STEP(3);

	x4 = init_vec(1.0f,1.0f,1.0f,1.0f);
	A4 = init_mat(1.0f,0.0f,0.0f,0.0f,
		      0.0f,1.0f,0.0f,0.0f,
		      0.0f,0.0f,1.0f,0.0f,
		      0.0f,0.0f,0.0f,1.0f);

	STEP(4);

	y2 = A2 * x2;

	STEP(5);

	y3 = A3 * x3;

	STEP(6);

	y4 = A4 * x4;

	STEP(7);

	return 0;
}










///////////////////////////////////////////////////////////////////
// EXPERIMENTAL ITERATOR INTERFACE
///////////////////////////////////////////////////////////////////

// scalair
template <class T> inline T foo(const T& x) { return sin(x); }

// vector func
template <class T,int N>
inline vec<T,N> foo(const vec<T,N>& x) {
	vec<T,N> out;

	for(int i=0;i<N;i++)
		out[i] = foo(x[i]);

	return out;
}


#define VEC_FUN00(__func_name)                                                \
template <class RandomAccessIterator>                                         \
inline void __func_name(RandomAccessIterator begin, RandomAccessIterator end, \
		RandomAccessIterator out)                                     \
{                                                                             \
	while (begin!=end)                                                    \
		*out++ = __func_name(*begin++);                               \
}
//VEC_FUNC00(foo)




float foo_test()
{
	static float result = 0;
	
	float x=1.34f, y;
	vec<float,10> vx(x),vy;

	STEP(1);
	
	y = foo(x);			// scalair form
	result += y;

	STEP(2);

	vy = foo(vx);			// vector form
	result += vy[0];

	STEP(3);
	
//	foo(vx.begin(),vx.end(), vy.begin()); // iterator form
	result += vy[0];
	
//	foo(&vx[0], &vx[10], vy.begin()); // this syntax is also okay :)
	result += vy[0];

	STEP(4);
	
	vec3 v3x(x), v3y;
//	foo(v3x.begin(),v3x.end(), v3y.begin());
	result += v3y[0];

	STEP(5);

	std::vector<float> std_x,std_y;
//	foo(std_x.begin(),std_x.end(),std_y.begin());
	
	return result;
}



